package com.myhome.demo;

import java.util.HashMap;
import java.util.Map;

/*
 * 线程本地变量是和线程相关的变量，一个线程则一份数据。我们通过ThreadLocal保存的数据最终是保存在Thread类的ThreadLocalMap
 * threadLocals变量中。ThreadlocalMap是一个Map结构，其中key为我们声明的ThreadLocal对象，value即为我们使用ThreadLocal保存的线程本地变量.
 * 当我们调用ThreadLocal变量set方法时，那么为将TheadLocal作为key,set方法的参数做为value保存在当前线程的threadLocals中.调用get方法时类似，
 * 调用get方法时，会去Thread的threadLocals中去寻找key为ThreadLocal 变量的值
 * https://segmentfault.com/a/1190000007647833
 * http://www.iteye.com/topic/103804
 * */
public class ThreadLocalsDemo {
    static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

    static ThreadLocal<Map> paramThreadLocal = new ThreadLocal<>();

    public void test01() {
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                stringThreadLocal.set("threadName===>" + Thread.currentThread().getName());
                System.out.println(this.getName() + " thread get the value:" + stringThreadLocal.get());
            }
        };
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                stringThreadLocal.set("threadName===>" + Thread.currentThread().getName());
                System.out.println(this.getName() + " thread get the value:" + stringThreadLocal.get());
            }
        };
        Thread thread3 = new Thread() {
            @Override
            public void run() {
                stringThreadLocal.set("threadName===>" + Thread.currentThread().getName());
                System.out.println(this.getName() + " thread get the value:" + stringThreadLocal.get());
            }
        };

        thread1.start();
        thread2.start();
        thread3.start();
        System.out.println("main线程调用set方法之前：" + stringThreadLocal.get());
        stringThreadLocal.set("main 线程set的值");
        System.out.println("main线程调用set方法之后：" + stringThreadLocal.get());
    }

    public void test02() {
        //参数主要利用ThreadLocal是线程的局部变量，只要在同一个线程中，之前设置的值后面就能取到，从而达到参数值传递的效果。
        //在前面在线程变量中添加值
        stringThreadLocal.set("name");
        paramThreadLocal.set(new HashMap() {
            {
                put("id", "1");
                put("name", "xiaoTOT");
                put("gender", "M");
            }
        });
        testParam();
    }

    private void testParam() {
        //从线程本地变量获取参数
        Map map = paramThreadLocal.get();
        map.forEach((key, value) -> {
            System.out.println("key:" + key + " & value=" + value);
        });
    }

    public void test03() {
        // 单例模式的好处无用质疑，可以减少对象的创建。对于那些创建非常费时的对象尤其明显。并且如果能够用单例解决的问题经历使用单例解决，
        // 这样能减轻运行时的压力。
        // 1,对于一个对象倘若没有成员变量，单例非常简单，不用去担心多线程同时对成员变量修改而产生的线程安全问题。
        // 2,对于一个拥有成员变量的对象使用单例就需要考虑到线程安全问题。多线程访问又可以分为下面两个方面：
        // a：成员变量需要多线程同步，比如账户对象（ACCOUNT）中的成员变量余额(amount).amount成员变量需要在多线程的访问下保证
        // 各个线程保证绝对的同步，即无论什么时候线程内的值都是一样。
        // 我们可以通过加同步关键字synchronized,volatile来解决。
        // b,成员变量不需要线程同步，每个线程访问自己线程内部的对象。比如一个服务类对数据库的链接成员变量，每个线程分配一个连接即可。
        // 类似这种场景，我们最简单的方式是使用多例模式来解决。但更好的方式是通过threadLocal来解决。

        //多例模式
        Thread thread = new Thread() {
            @Override
            public void run() {
                DBConnect dbConnect = new DBConnect();
                DemoService demoService = new DemoService();
                demoService.setConnect(dbConnect);
                demoService.doSomeThing();
            }
        };

        Thread thread2 = new Thread() {
            @Override
            public void run() {
                DBConnect dbConnect = new DBConnect();
                DemoService demoService = new DemoService();
                demoService.setConnect(dbConnect);
                demoService.doSomeThing();
            }
        };

        thread.start();
        thread2.start();

        // 单例模式改造
        // 由DemoService构造器可以看出，构造这个对象是非常耗时的。并且还不能使用单例模式，因为DBConnect是不能多线程访问的。
        // 遇到这种情况那就使用ThreadLocal来改造吧。
        //如果能修改DemoService源码，修改源码即可。若不能修该源码（比如DemoService是一个三方包）单DemoService不是final的，
        // 即可以通过继承修改。
        DemoService demoService1 = new ThreadLocalDemoService();
        Thread threadA = new Thread() {
            @Override
            public void run() {
                demoService1.setConnect(new DBConnect());
                demoService1.doSomeThing();
            }
        };
        Thread threadB = new Thread() {
            @Override
            public void run() {
                demoService1.setConnect(new DBConnect());
                demoService1.doSomeThing();
            }
        };
        threadA.start();
        threadB.start();
    }

    static class DemoService {
        //这个对象不能线程同时访问，应该是一个线程就建立一个连接到数据库。不同的线程不能使用同一个连接。
        DBConnect connect;

        public DemoService() {
            try {
                Thread.sleep(5l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void setConnect(DBConnect connect) {
            this.connect = connect;
        }

        public void doSomeThing() {
            connect.updateSomeData();
        }
    }

    //使用ThreadLocal改造成员变量，使其可以使其可以使用单例模式
    static class ThreadLocalDemoService extends DemoService {
        ThreadLocal<DBConnect> connectThreadLocal = new ThreadLocal<>();

        public ThreadLocalDemoService() {
            super();
        }

        public void doSomeThing() {
            connectThreadLocal.get().updateSomeData();
        }

        public void setConnect(DBConnect dbConnect) {
            connectThreadLocal.set(dbConnect);
        }
    }

    class DBConnect {
        private String transactionName = Thread.currentThread().getName() + "的事务";

        public void updateSomeData() {
            System.out.println(transactionName + " update some data");
        }
    }

    public static void main(String[] args) {
        ThreadLocalsDemo localsDemo = new ThreadLocalsDemo();
        localsDemo.test03();
    }
}
